/*
    $Id$
    MAR another horizontal scroll action game
    Copyright (C) 2009 Japan Manoretimedia Lab.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "collision.h"
#include "graphics.h"
#include "util.h"
#include <assert.h>
#include <math.h>

#define COLLISION_ELEM_NUM (1024)

#define COLLISION_EPSILON (0.01f)

const wchar_t *
CollisionResultTypeToStr(CollisionResultType t)
{
#ifdef _DEBUG
    switch (t) {
    case CR_NotCollided: return L"CR_NotCollided";
    case CR_Bounced:     return L"CR_Bounced";
    case CR_Lose:        return L"CR_Lose";
    case CR_Win:         return L"CR_Win";
    default:
        assert(0);
        return L"unknown";
    }
#else
    return L"";
#endif
}

struct CollisionElem {
    int                 id;
    bool                use;
    CollisionType       type;
    CollisionResultType result;
    XY                  posLB;
    WH                  wh;
    XY                  v;
    bool                landed;

    float Top(void) const {
        return posLB.y + wh.h;
    }
    float Bottom(void) const {
        return posLB.y;
    }
    float Left(void) const {
        return posLB.x;
    }
    float Right(void) const {
        return posLB.x + wh.w;
    }
};

struct Collision {
    int frameNumber;
    CollisionElem elem[COLLISION_ELEM_NUM];
};

static CollisionElem *
FindUnusedCollisionElem(Collision *self)
{
    assert(self);

    for (int i=0; i<COLLISION_ELEM_NUM; ++i) {
        if (!self->elem[i].use) {
            self->elem[i].use = true;
            return &self->elem[i];
        }
    }

    return NULL;
}

static CollisionElem *
CollisionElemGet(Collision *self, int collisionId)
{
    assert(self);

    if (collisionId < 0 || COLLISION_ELEM_NUM <= collisionId) {
        return NULL;
    }

    return &self->elem[collisionId];
}

Collision *
Collision_Instance(void)
{
    static Collision self;

    return &self;
}

void
Collision_Init(Collision *self)
{
    assert(self);

    for (int i=0; i<COLLISION_ELEM_NUM; ++i) {
        self->elem[i].id = i;
        self->elem[i].use = false;
    }
}

void
Collision_Term(Collision *self)
{
    assert(self);

    (void)self;
}

void
Collision_Update(Collision *self)
{
    assert(self);

    for (int i=0; i<COLLISION_ELEM_NUM; ++i) {
        self->elem[i].result = CR_NotCollided;
    }

    ++self->frameNumber;
}

int
Collision_Register(Collision *self, const XY &posLB, const WH &wh, CollisionType t)
{
    assert(self);

    CollisionElem *ce = FindUnusedCollisionElem(self);
    assert(ce);

    ce->type    = t;
    ce->result  = CR_NotCollided;
    ce->posLB   = posLB;
    ce->wh      = wh;
    ce->v       = XY(0, 0);
    ce->landed = true;

    return ce->id;
}

int
Collision_RegisterS(Collision *self, float px, float py, float w, float h, CollisionType t)
{
    return Collision_Register(self, XY(px, py), WH(w, h), t);
}

void
Collision_Unregister(Collision *self, int collisionId)
{
    assert(self);

    CollisionElem *ce = CollisionElemGet(self, collisionId);
    assert(ce);

    ce->use = false;
}

static void
CollidedHorizontally(CollisionElem *me, CollisionElem *you, XY &v)
{
    if (you->type == CT_Player &&
        me->type != CT_Player) {
        v.Inverse();
        CollidedHorizontally(you, me, v);
        v.Inverse();
        return;
    }

    switch (me->type) {
    case CT_Player:
        switch (you->type) {
        case CT_Brick:
        case CT_Player:
            me->result  = CR_Bounced;
            you->result = CR_Bounced;
            me->landed = false;
            if (me->Left() < you->Left()) {
                v.x = you->Left() - me->Right();
                GR_DrawRect(GR_Instance(), me->posLB,  me->wh,  0.2f, 0xff00ff00);
            } else {
                v.x = you->Right() - me->Left();
                GR_DrawRect(GR_Instance(), you->posLB, you->wh, 0.2f, 0xffff00ff);
            }
            /*
            DebugPrintf(L"CH me%d=%s you%d=%s\n",
                me->id, CollisionResultTypeToStr(me->result),
                you->id, CollisionResultTypeToStr(you->result));
                */
            return;
        case CT_Kuribou:
            if (you->Bottom() < me->Bottom()) {
                // player definitely higher position than kuribou
                me->result  = CR_Win;
                you->result = CR_Lose;
            } else {
                me->result  = CR_Lose;
                you->result = CR_Win;
            }
            return;
        default: assert(0); return;
        }
    case CT_Kuribou:
        switch (you->type) {
        case CT_Brick:
        case CT_Kuribou:
            me->result  = CR_Bounced;
            you->result = CR_Bounced;
            return;
        default: 
            assert(0);
            return;
        }
    default:
        assert(0);
        return;
    }
}

static void
CollidedVertically(CollisionElem *me, CollisionElem *you, XY &v)
{
    if (you->type == CT_Player &&
        me->type != CT_Player) {
        v.Inverse();
        CollidedVertically(you, me, v);
        v.Inverse();
        return;
    }

    switch (me->type) {
    case CT_Player:
        switch (you->type) {
        case CT_Brick:
            if (you->Bottom() < me->Bottom()) {
                // player definitely higher position than brick
                me->result  = CR_Bounced;
                you->result = CR_Bounced;
                v.y = you->Top() - me->Bottom();
                me->landed = true;
                GR_DrawRect(GR_Instance(), me->posLB,  me->wh,  0.2f, 0xffffff00);
                GR_DrawRect(GR_Instance(), you->posLB, you->wh, 0.2f, 0xffffff00);
            } else {
                me->result  = CR_Win;
                you->result = CR_Lose;
                me->landed = false;
                GR_DrawRect(GR_Instance(), me->posLB,  me->wh,  0.2f, 0xffff0000);
                GR_DrawRect(GR_Instance(), you->posLB, you->wh, 0.2f, 0xff0000ff);
            }
            /*
            DebugPrintf(L"CV me%d=%s you%d=%s\n",
                me->id, CollisionResultTypeToStr(me->result),
                you->id, CollisionResultTypeToStr(you->result));
                */
            return;
        case CT_Player:
            me->result  = CR_Bounced;
            you->result = CR_Bounced;
            return;
        case CT_Kuribou:
            if (you->Bottom() < me->Bottom()) {
                // player definitely higher position than kuribou
                me->result  = CR_Win;
                you->result = CR_Lose;
            } else {
                me->result  = CR_Lose;
                you->result = CR_Win;
            }
            return;
        default: assert(0); return;
        }
    case CT_Kuribou:
        switch (you->type) {
        case CT_Brick:
        case CT_Kuribou:
            me->result  = CR_Bounced;
            you->result = CR_Bounced;
            if (you->Bottom() < me->Bottom()) {
                v.y = you->Top() - me->Bottom();
            } else {
                v.y = you->Bottom() - me->Top();
            }
            return;
        default: 
            assert(0);
            return;
        }
    default:
        assert(0);
        return;
    }
}

void
Collision_Move(Collision *self, int collisionId, const XY &vWant)
{
    assert(self);

    CollisionElem *me = CollisionElemGet(self, collisionId);
    assert(me);

    me->landed = false;

    XY v(vWant);

    XY meNewPosLB;
    meNewPosLB.x = me->posLB.x + v.x;
    meNewPosLB.y = me->posLB.y + v.y;

    Rect meRect(me->posLB, me->wh);
    Rect meNewRect(meNewPosLB, me->wh);

    for (int i=0; i<COLLISION_ELEM_NUM; ++i) {
        CollisionElem *ce = &self->elem[i];
        if (collisionId == i ||
            !ce->use ||
            CR_Lose == ce->result ||
            CR_Win  == ce->result) {
            continue;
        }

        Rect overlap = meNewRect.GetOverlap(Rect(ce->posLB, ce->wh));
        if (overlap.IsPositive()) {
            // collided.
            /*
            DebugPrintf(L"v(%f,%f) me(%f,%f)-(%f,%f) overlap(%f,%f)-(%f,%f)\n",
                v.x, v.y,
                meRect.left, meRect.bottom, meRect.right, meRect.top,
                overlap.left, overlap.bottom, overlap.right, overlap.top);
            */

            if (((-COLLISION_EPSILON < (meRect.bottom - overlap.top)) ||
                 (-COLLISION_EPSILON < (overlap.bottom - meRect.top))) &&
                (((meRect.left < overlap.right + COLLISION_EPSILON) &&
                  (overlap.right < meRect.right + COLLISION_EPSILON)) ||
                 ((meRect.left < overlap.left + COLLISION_EPSILON) &&
                  (overlap.left < meRect.right + COLLISION_EPSILON)))) {
                CollidedVertically(me, ce, v);
            } else {
                CollidedHorizontally(me, ce, v);
            }
        }
    }

    me->v = v;
    me->posLB.x += v.x;
    me->posLB.y += v.y;
}

void
Collision_MoveS(Collision *self, int collisionId, float vWantX, float vWantY)
{
    Collision_Move(self, collisionId, XY(vWantX, vWantY));
}

float
Collision_ResultGetPosLeftX(Collision *self, int collisionId)
{
    assert(self);

    CollisionElem *me = CollisionElemGet(self, collisionId);
    assert(me);

    return me->posLB.x;
}

float
Collision_ResultGetPosBottomY(Collision *self, int collisionId)
{
    assert(self);

    CollisionElem *me = CollisionElemGet(self, collisionId);
    assert(me);

    return me->posLB.y;
}

CollisionResultType
Collision_ResultGetResultType(Collision *self, int collisionId)
{
    assert(self);

    CollisionElem *me = CollisionElemGet(self, collisionId);
    assert(me);

    return me->result;
}

float
Collision_ResultGetVX(Collision *self, int collisionId)
{
    assert(self);

    CollisionElem *me = CollisionElemGet(self, collisionId);
    assert(me);

    return me->v.x;
}

float
Collision_ResultGetVY(Collision *self, int collisionId)
{
    assert(self);

    CollisionElem *me = CollisionElemGet(self, collisionId);
    assert(me);

    return me->v.y;
}

bool
Collision_ResultIsLanded(Collision *self, int collisionId)
{
    assert(self);

    CollisionElem *me = CollisionElemGet(self, collisionId);
    assert(me);

    if (!me->landed) {
        DebugPrintf(L"hovering\n");
    }

    return me->landed;
}
